/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
* Name: directx_audio_sink.cpp
*
* Purpose: audio sink component implementation with directx audio.
*
* Developer:
*   wen.gu , 2018-09-05
*
* TODO:
*
***************************************************************************/

/******************************************************************************
**    INCLUDES
******************************************************************************/
#define _CRT_SECURE_NO_WARNINGS

#include "directx_audio_sink.h"
#include "mp_utils.h"

#define LOG_TAG "dx_audio"
#include "mp_log.h"


#pragma comment(lib, "winmm.lib")

/******************************************************************************
**    MACROS
******************************************************************************/

#define DX_COMP_NAME "directx audio"
#define DX_COMP_VER "00.01.00"


/******************************************************************************
**    VARIABLE DEFINITIONS
******************************************************************************/

/******************************************************************************
**   inner FUNCTION DEFINITIONS
******************************************************************************/
static void CALLBACK waveoutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwIntance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
    DxAudioSink* mda = (DxAudioSink*)dwIntance;
    LOGD("got message:0x%08x\n", uMsg);

    if (WOM_DONE == uMsg)
    {
        mda->onWaveDone((WaveHdrNode*)dwParam1);
    }
}

/******************************************************************************
**    FUNCTION DEFINITIONS
******************************************************************************/




DxAudioSink::DxAudioSink()
    :MPAudioSink(DX_COMP_NAME, DX_COMP_VER)
{
    /** todo something */
}

DxAudioSink::~DxAudioSink()
{
    /** todo something */
    WaveOutClose();
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

const MPTrackInfo& DxAudioSink::getFormat()
{
    return mInfo;
}

MPError DxAudioSink::start()
{
    return WaveOutResume();
}

MPError DxAudioSink::pause()
{
    return WaveOutPause();
}

MPError DxAudioSink::flush(eAudioSinkFlush do_what /*= eAudioSinkFlush::Drain*/)
{
    return WaveOutFlush(do_what);
}


/** if MPBuffer::size() == 0 or MPBuffer::offset() == MPBuffer::capacity(),
*  then current buffer already write complete
*/
MPError DxAudioSink::write(MPBuffer* data)
{
    return WaveOutWrite(data);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

MPError DxAudioSink::initialize(const MPTrackInfo& info)
{
    MPError ret = WaveOutOpen(info);

    if (MP_OK != ret)
    {
        LOGE("create direct audio instance failed\n");
        ret = MP_ErrorInsufficientResources;
    }

    return ret;
}

void DxAudioSink::onWaveDone(WaveHdrNode* p_waveheader)
{
    AutoLock al(mLock);
    p_waveheader->next = mFreeList;
    mFreeList = p_waveheader;
    mFramesCnt--;
    mCond.notify_all();
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
MPError DxAudioSink::WaveOutOpen(const MPTrackInfo& info)
{
    HWAVEOUT hWaveOut = NULL;

    const MPAudioInfo& audioInfo = info.audio;

    MMRESULT mRet = 0;
    int nRet = waveOutGetNumDevs();
    WAVEFORMATEX wavFmt;


    for (int i = 0; i < nRet; i++)
    {
        WAVEOUTCAPS woc;
        waveOutGetDevCaps(i, &woc, sizeof(woc));

        LOGD("#%d\tdevice name: %s\n", i, woc.szPname);
    }


    wavFmt.wFormatTag = WAVE_FORMAT_PCM;
    wavFmt.nChannels = audioInfo.channelCount;
    wavFmt.nSamplesPerSec = audioInfo.samplerate;
    wavFmt.nAvgBytesPerSec = audioInfo.samplerate * audioInfo.bitDepth * audioInfo.channelCount / 8;
    wavFmt.nBlockAlign = audioInfo.bitDepth * audioInfo.channelCount / 8;
    wavFmt.wBitsPerSample = audioInfo.bitDepth;
    wavFmt.cbSize = 0;

    mRet = waveOutOpen(&hWaveOut, WAVE_MAPPER, &wavFmt, (DWORD_PTR)waveoutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);

    WAVEOUTCAPS woc;
    mRet = waveOutGetDevCaps((UINT_PTR)hWaveOut, &woc, sizeof(woc));

    mWaveOut = hWaveOut;

    //waveOutPrepareHeader(hWaveOut, hdr, sizeof(WAVEHDR));

    LOGD("we opened device name is: %s\n", woc.szPname);
    return MP_OK;
}

void DxAudioSink::WaveOutClose()
{
    HWAVEOUT hWaveOut = mWaveOut;
    if (hWaveOut)
    {
        /* Before calling waveOutClose we must reset the device */
        MMRESULT result = waveOutReset(hWaveOut);
        if (result != MMSYSERR_NOERROR)
        {
            LOGE("waveOutReset failed 0x%x\n", result);
            /*
            now we must wait, that all buffers are played
            because cancel doesn't work in this case...
            */
            if (result == MMSYSERR_NOTSUPPORTED)
            {
                /*
                clear currently played (done) buffers,
                if returnvalue > 0 (means some buffer still playing)
                wait for the driver event callback that one buffer
                is finished with playing, and check again
                the timeout of 5000ms is just, an emergency exit
                of this loop, to avoid deadlock in case of other
                (currently not known bugs, problems, errors cases?)
                */
                WaveOutFlush(eAudioSinkFlush::Drain);
            }
        }

        /* wait for the frames to be queued in cleaning list */
        WaveOutFlush(eAudioSinkFlush::Drain);
        WaveOutClean();

        /* now we can Close the device */
        result = waveOutClose(hWaveOut);
        if (result != MMSYSERR_NOERROR)
        {
            LOGE("waveOutClose failed(0x%x)\n", result);
        }
    }
}


MPError DxAudioSink::WaveOutPause()
{
    MMRESULT mRet = 0;
    MPError ret = MP_OK;
    mRet = waveOutPause(mWaveOut);

    if (mRet != MMSYSERR_NOERROR)
    {
        LOGE("waveOutPause failed(0x%x)\n", mRet);
        ret = MP_ErrorUndefined;

    }

    return ret;
}

MPError DxAudioSink::WaveOutResume()
{
    MMRESULT mRet = 0;
    MPError ret = MP_OK;
    mRet = waveOutRestart(mWaveOut);

    if (mRet != MMSYSERR_NOERROR)
    {
        LOGE("waveOutRestart failed(0x%x)\n", mRet);
        ret = MP_ErrorUndefined;

    }

    return ret;
}

MPError DxAudioSink::WaveOutFlush(eAudioSinkFlush do_what)
{
    MPError ret = MP_OK;
    MMRESULT mRet = 0;

    if (do_what == eAudioSinkFlush::Drain)
    {
        LOGD("==>:left block number(%d)\n", mFramesCnt);
        AutoLock al(mLock);
        while (mFramesCnt)
        {
            mCond.wait(al);
        }
    }
    else
    {
        mRet = waveOutReset(mWaveOut);

        if (mRet != MMSYSERR_NOERROR)
        {
            LOGE("waveOutReset failed(%d)\n", mRet);
            ret = MP_ErrorUndefined;
        }
    }


    return ret;
}

MPError DxAudioSink::PlayWaveOut(WaveHdrNode *p_waveheader, MPBuffer *p_buffer)
{
    MMRESULT result;
    HWAVEOUT h_waveout = mWaveOut;
    MPError ret = MP_OK;
    WAVEHDR& hdr = p_waveheader->hdr;
    /* Prepare the buffer */

    hdr.lpData = (LPSTR)p_buffer->data();
    hdr.dwBufferLength = p_buffer->size();


    hdr.dwUser = p_buffer ? (DWORD_PTR)p_buffer : (DWORD_PTR)1;
    hdr.dwFlags = 0;

    result = waveOutPrepareHeader(h_waveout, &hdr, sizeof(WAVEHDR));
    if (result == MMSYSERR_NOERROR)
    {
        /* Send the buffer to the waveOut queue */
        result = waveOutWrite(h_waveout, &p_waveheader->hdr, sizeof(WAVEHDR));
        if (result != MMSYSERR_NOERROR)
        {
            LOGE("waveOutWrite failed(0x%x)\n", result);
            ret = MP_ErrorUndefined;
        }
    }
    else
    {
        LOGE("waveOutPrepareHeader failed(0x%x)\n", result);
        ret = MP_ErrorUndefined;
    }

    return ret;
}

MPError DxAudioSink::WaveOutWrite(MPBuffer* buffer)
{
    MPError ret = MP_ErrorBadParameter;
    MMRESULT mRet = 0;
    MPBuffer* frame = buffer;

    if (buffer)
    {
        WaveHdrNode* node = (WaveHdrNode*)malloc(sizeof(WaveHdrNode));

        if (node)
        {
            node->next = nullptr;

            while (PlayWaveOut(node, buffer) != MP_OK)
            {
                LOGE("Couln't write frame... sleeping");
                MPSleepMs(50);
            }

            WaveOutClean();

            AutoLock al(mLock);
            mFramesCnt++;
        }
        else
        {
            LOGE("alloc WAVEHDR failed\n");
            buffer->release();
            ret = MP_ErrorInsufficientResources;
        }
    }

    return ret;
}

void DxAudioSink::WaveOutClean()
{
    WaveHdrNode *p_whdr, *p_list;

    AutoLock al(mLock);

    p_list = mFreeList;
    mFreeList = nullptr;


    while (p_list)
    {
        p_whdr = p_list;
        p_list = p_list->next;
        WaveOutClearBuffer(&p_whdr->hdr);
        free(p_whdr);
    }
}

void DxAudioSink::WaveOutClearBuffer(WAVEHDR *p_waveheader)
{
    MPBuffer *p_buffer = (MPBuffer *)(p_waveheader->dwUser);
    /* Unprepare and free the buffers which has just been played */
    waveOutUnprepareHeader(mWaveOut, p_waveheader, sizeof(WAVEHDR));

    if (p_waveheader->dwUser != 1)
    {
        p_buffer->release();
    }
}
